﻿using Marmat.Forms.Skin;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Marmat.Forms.Skin
{
    public partial class Form : System.Windows.Forms.Form
    {
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        private struct NonClientSizeInfo
        {
            public Size CaptionButtonSize;
            public Size BorderSize;
            public int CaptionHeight;
            public Rectangle CaptionRect;
            public Rectangle Rect;
            public Rectangle ClientRect;
            public int Width;
            public int Height;
        }

        private const int WM_NCACTIVATE = 0x86;
        private const int WM_NCPAINT = 0x85;
        private const int WM_NCLBUTTONDOWN = 0xA1;
        private const int WM_NCRBUTTONDOWN = 0xA4;
        private const int WM_NCRBUTTONUP = 0xA5;
        private const int WM_NCMOUSEMOVE = 0xA0;
        private const int WM_NCLBUTTONUP = 0xA2;
        private const int WM_NCCALCSIZE = 0x83;
        private const int WM_NCMOUSEHOVER = 0x2A0;
        private const int WM_NCMOUSELEAVE = 0x2A2;
        private const int WM_NCHITTEST = 0x84;
        private const int WM_NCCREATE = 0x81;
        private const int WM_NCLBUTTONDBLCLK = 163;

        private const int WM_LBUTTONDOWN = 0x201;
        private const int WM_CAPTURECHANGED = 0x215;
        private const int WM_LBUTTONUP = 0x202;
        private const int WM_SETCURSOR = 0x20;
        private const int WM_CLOSE = 0x10;
        private const int WM_SYSCOMMAND = 0x112;
        private const int WM_MOUSEMOVE = 0x200;
        private const int WM_SIZE = 0x5;
        private const int WM_SIZING = 0x214;
        private const int WM_GETMINMAXINFO = 0x24;
        private const int WM_ENTERSIZEMOVE = 0x231;
        private const int WM_WINDOWPOSCHANGING = 0x46;


        // FOR WM_SIZING MSG WPARAM
        private const int WMSZ_BOTTOM = 6;
        private const int WMSZ_BOTTOMLEFT = 7;
        private const int WMSZ_BOTTOMRIGHT = 8;
        private const int WMSZ_LEFT = 1;
        private const int WMSZ_RIGHT = 2;
        private const int WMSZ_TOP = 3;
        private const int WMSZ_TOPLEFT = 4;
        private const int WMSZ_TOPRIGHT = 5;

        // 鼠标左键。
        private const int MK_LBUTTON = 0x1;

        private const int SC_CLOSE = 61536;
        private const int SC_MAXIMIZE = 61488;
        private const int SC_MINIMIZE = 61472;
        private const int SC_RESTORE = 61728;
        private const int SC_CONTEXTHELP = 61824;

        private const int HTCAPTION = 2;
        private const int HTCLOSE = 20;
        private const int HTHELP = 21;
        private const int HTMAXBUTTON = 9;
        private const int HTMINBUTTON = 8;
        private const int HTTOP = 12;

        private const int SM_CYBORDER = 6;
        private const int SM_CXBORDER = 5;
        private const int SM_CYCAPTION = 4;

        private const int CS_DropSHADOW = 131072;
        private const int GCL_STYLE = (-26);

        #region "Windows API"

        [DllImport("User32.dll")]
        private extern static IntPtr GetWindowDC(IntPtr hwnd);

        [DllImport("User32.dll")]
        private extern static IntPtr ReleaseDC(IntPtr hwnd, IntPtr hdc);

        [DllImport("User32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private extern static bool GetWindowRect(IntPtr hwnd, ref RECT rect);

        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private extern static int SetClassLong(IntPtr hwnd, int nIndex, int dwNewLong);

        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private extern static int GetClassLong(IntPtr hwnd, int nIndex);
        #endregion

        public Form()
        {
            InitializeComponent();
            SetStyle(ControlStyles.ResizeRedraw, true); //尺寸变化后绘制
            SetStyle(ControlStyles.Opaque, false); //改善绘图质量
            SetStyle(ControlStyles.SupportsTransparentBackColor, true); //使控件支持透明色
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);

            this.CloseButtonImage.Size = new System.Drawing.Size(39, 20);
            this.CloseButtonImage.Down = Skin.Properties.Resources.btn_close_down;
            this.CloseButtonImage.Hover = Skin.Properties.Resources.btn_close_highlight;
            this.CloseButtonImage.Normal = Skin.Properties.Resources.btn_close_disable;
            this.MaximumButtonImage.Down = Skin.Properties.Resources.btn_max_down;
            this.MaximumButtonImage.Hover = Skin.Properties.Resources.btn_max_highlight;
            this.MaximumButtonImage.Normal = Skin.Properties.Resources.btn_max_normal;
            this.RestoreButtonImage.Down = Skin.Properties.Resources.btn_restore_down;
            this.RestoreButtonImage.Hover = Skin.Properties.Resources.btn_restore_highlight;
            this.RestoreButtonImage.Normal = Skin.Properties.Resources.btn_restore_normal;
            this.MinimumButtonImage.Down = Skin.Properties.Resources.btn_mini_down;
            this.MinimumButtonImage.Hover = Skin.Properties.Resources.btn_mini_highlight;
            this.MinimumButtonImage.Normal = Skin.Properties.Resources.btn_mini_normal;
            this.SizeChanged += this.Form_SizeChanged;
        }

        NonClientSizeInfo GetNonClientInfo(IntPtr hwnd)
        {
            NonClientSizeInfo info = new NonClientSizeInfo();
            info.CaptionButtonSize = SystemInformation.CaptionButtonSize;
            info.CaptionHeight = SystemInformation.CaptionHeight;
            switch (FormBorderStyle)
            {
                case FormBorderStyle.FixedSingle:
                case FormBorderStyle.Fixed3D:
                case FormBorderStyle.FixedDialog:
                    info.BorderSize = SystemInformation.FixedFrameBorderSize;
                    break;
                case FormBorderStyle.Sizable:
                    info.BorderSize = SystemInformation.FrameBorderSize;
                    break;
                case FormBorderStyle.FixedToolWindow:
                    info.BorderSize = SystemInformation.FixedFrameBorderSize;
                    info.CaptionButtonSize = SystemInformation.ToolWindowCaptionButtonSize;
                    info.CaptionHeight = SystemInformation.ToolWindowCaptionHeight;
                    break;
                case FormBorderStyle.SizableToolWindow:
                    info.CaptionButtonSize = SystemInformation.ToolWindowCaptionButtonSize;
                    info.BorderSize = SystemInformation.FrameBorderSize;
                    info.CaptionHeight = SystemInformation.ToolWindowCaptionHeight;
                    break;
                default:
                    info.BorderSize = SystemInformation.BorderSize;
                    break;
            }
            int size = 8;
            info.BorderSize = new Size(size, size);//临时加上去的
            RECT areatRect = new RECT();
            GetWindowRect(hwnd, ref areatRect);

            int width = areatRect.Right - areatRect.Left;
            int height = areatRect.Bottom - areatRect.Top;

            info.Width = width;
            info.Height = height;

            Point xy = new Point(areatRect.Left, areatRect.Top);
            xy.Offset(-areatRect.Left, -areatRect.Top);
            info.CaptionRect = new Rectangle(xy.X, xy.Y + info.BorderSize.Height, width, info.CaptionHeight + (this.DesignMode ? 0 : 2));
            info.Rect = new Rectangle(xy.X, xy.Y, width, height);
            info.ClientRect = new Rectangle(xy.X + info.BorderSize.Width, xy.Y + info.CaptionHeight + info.BorderSize.Height, width - info.BorderSize.Width * 2, height - info.CaptionHeight - info.BorderSize.Height * 2);
            return info;
        }

        /// <summary>
        /// 背景绘制的临时图像，用于绘制客户区
        /// </summary>
        private Bitmap back_bitmap;
        private Dictionary<FormButton, Rectangle> buttonRectangle = new Dictionary<FormButton, Rectangle>();

        /// <summary>
        /// 绘制标题
        /// </summary>
        /// <param name="hwnd"></param>
        /// <param name="state"></param>
        public void DrawCaption(IntPtr hwnd, FormMouseState state)
        {
            IntPtr dc = GetWindowDC(hwnd);
            using (Graphics graphics = Graphics.FromHdc(dc))
            {
                NonClientSizeInfo info = GetNonClientInfo(hwnd);
                Bitmap bitmap = new Bitmap(this.Width, this.Height);
                using (Graphics g = Graphics.FromImage(bitmap))
                {
                    Rectangle clipRectangle = new Rectangle(0, 0, this.Width - 1, this.Height - 1);
                    PaintEventArgs args = new PaintEventArgs(g, clipRectangle);
                    if (this.DrawSkinForm != null)
                        this.DrawSkinForm.Invoke(this, args);
                    else if (this.SkinBackgroundImage != null && this.DrawSkinForm is null)
                        g.DrawImage(this.SkinBackgroundImage, clipRectangle);
                    SmoothingMode smoothing = graphics.SmoothingMode;
                    graphics.SmoothingMode = SmoothingMode.HighQuality;
                    this.DrawFormBorder(args);
                    graphics.SmoothingMode = smoothing;
                    this.DrawTitle(g, info);
                    this.DrawControlBox(g, info, state);
                }
                graphics.ExcludeClip(info.ClientRect);
                graphics.DrawImage(bitmap, 0, 0);
                this.back_bitmap?.Dispose();
                this.back_bitmap = bitmap;
            }
            ReleaseDC(hwnd, dc);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            // 绘制客户区
            if (this.DrawClientArea && this.back_bitmap != null)
            {
                NonClientSizeInfo Info = GetNonClientInfo(this.Handle);
                Rectangle srcRect = new Rectangle(Info.ClientRect.X, Info.ClientRect.Y, Info.ClientRect.Width, Info.ClientRect.Height);
                Rectangle destRect = new Rectangle(0, 0, this.ClientSize.Width, this.ClientSize.Height);
                e.Graphics.DrawImage(this.back_bitmap, destRect, srcRect, GraphicsUnit.Pixel);
            }
            base.OnPaint(e);
        }

        /// <summary>
        /// 窗口边框的画笔
        /// </summary>
        private readonly Pen _borderPen = new Pen(Color.FromArgb(54, 108, 179));
        /// <summary>
        /// 绘制窗口边框
        /// </summary>
        /// <param name="e"></param>
        protected virtual void DrawFormBorder(PaintEventArgs e)
        {
            Rectangle rect = e.ClipRectangle;
            if (this.FormBorderStyle == FormBorderStyle.Sizable)
            {
                SmoothingMode smoothing = e.Graphics.SmoothingMode;
                e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
                var path = rect.GetRoundPath(5, 5, 0, 0);
                e.Graphics.DrawPath(this._borderPen, path);
                e.Graphics.SmoothingMode = smoothing;
            }
            else
            {
                e.Graphics.DrawRectangle(this._borderPen, rect);
            }
        }

        private void DrawControlBox(Graphics g, NonClientSizeInfo info, FormMouseState state)
        {
            buttonRectangle.Clear();
            if (this.ControlBox)
            {
                Point point = new Point(MousePosition.X - this.PointToScreen(new Point(0, 0)).X, MousePosition.Y - this.PointToScreen(new Point(0, 0)).Y);
                point.X += info.ClientRect.X;
                point.Y += info.ClientRect.Y;

                int left = info.BorderSize.Width;
                int top = this.WindowState == FormWindowState.Maximized ? 10 : 8;
                int width = this.WindowState == FormWindowState.Maximized ? 0 : 0;
                foreach (FormButton item in this.ButtonList)
                {
                    var rectangle = new Rectangle(info.CaptionRect.Width - left - item.Size.Width - width, top, item.Size.Width, item.Size.Height);
                    buttonRectangle[item] = rectangle;
                    left += item.Size.Width;
                    Image img = null;
                    if (state != FormMouseState.None && point != null && rectangle.Contains(point))
                    {
                        if (state == FormMouseState.Down && item.Down != null)
                            img = item.Down;
                        else if (state == FormMouseState.Hover && item.Hover != null)
                            img = item.Hover;
                        else if (item.Normal != null)
                            img = item.Normal;
                    }
                    else if (item.Normal != null)
                        img = item.Normal;
                    if (img != null)
                    {
                        Rectangle rect = new Rectangle(rectangle.X + (rectangle.Width - img.Width) / 2, rectangle.Y + (rectangle.Height - img.Height) / 2, img.Width, img.Height);
                        g.DrawImage(img, rect, new Rectangle(0, 0, img.Width, img.Height), GraphicsUnit.Pixel);
                    }
                    this.DrawFormButton?.Invoke(this, new DrawFormButtonEventArgs(g, rectangle, item));
                }
            }
        }

        void DrawTitle(Graphics g, NonClientSizeInfo info)
        {
            int titleX;
            if (this.ShowIcon && this.FormBorderStyle != FormBorderStyle.FixedToolWindow && this.FormBorderStyle != FormBorderStyle.SizableToolWindow)
            {
                Size iconSize = SystemInformation.SmallIconSize;
                g.DrawIcon(this.Icon, new Rectangle(new Point(info.BorderSize.Width, info.BorderSize.Height + (info.CaptionHeight - iconSize.Height) / 2), iconSize));
                titleX = info.BorderSize.Width + iconSize.Width + info.BorderSize.Width;
            }
            else
                titleX = info.BorderSize.Width;
            using (SolidBrush brush = new SolidBrush(this.ForeColor))
            {
                StringFormat format = new StringFormat();
                format.Trimming = StringTrimming.EllipsisCharacter;
                using (Font font = new Font("微软雅黑", 10.5f))
                {
                    g.DrawString(this.Text, Font, brush, titleX, info.BorderSize.Height + (info.CaptionRect.Height - Font.Height) / 2, format);
                }
            }
        }

        private int LOBYTE(long p)
        {
            return (int)(p & 65535);
        }
        private int HIBYTE(long p)
        {
            return System.Convert.ToInt32(p >> 16);
        }

        FormButton _oldFormButton;
        /// <summary>
        /// 处理 Windows 消息。
        /// </summary>
        /// <param name="m">要处理的 Windows System.Windows.Forms.Message。</param>
        protected override void WndProc(ref Message m)
        {
            if ((this.DrawSkinForm is null && this.SkinBackgroundImage is null) || (this.FormBorderStyle == FormBorderStyle.None || this.WindowState == FormWindowState.Minimized))
            {
                base.WndProc(ref m);
                return;
            }

            // 处理皮肤组件需要的消息机制
            switch (m.Msg)
            {
                case WM_NCPAINT:// 处理WM_NCPAINT消息可以实现自绘窗口边框和标题栏
                    if (m.WParam.ToInt32() == 1)
                        m.Result = new IntPtr(0);
                    else
                        m.Result = new IntPtr(1);
                    DrawCaption(m.HWnd, FormMouseState.None);
                    return;
                case WM_NCACTIVATE: // 负责画窗体被激活或者没有被激活时的样子
                    if (m.WParam.ToInt32() == 1)
                        m.Result = new IntPtr(0);
                    else
                        m.Result = new IntPtr(1);
                    DrawCaption(m.HWnd, FormMouseState.None);
                    return;
                case WM_NCRBUTTONDOWN: // 响应点击窗口标题栏时的事件
                    if (!this.DesignMode)
                    {
                        int posX;
                        int posY;
                        int wp = m.WParam.ToInt32();
                        long lp = m.LParam.ToInt64();
                        posX = this.LOBYTE(lp);
                        posY = HIBYTE(lp);
                        if (wp == HTCAPTION)
                        {
                            Point pt = this.PointToClient(new Point(posX, posY));
                            if (this.CaptionContextMenu != null)
                            {
                                this.CaptionContextMenu.Show(pt);
                                return;
                            }
                        }
                    }
                    break;
                case WM_SETCURSOR: // 鼠标移动OnMouseMOve就会自动发送 WM_SETCURSOR从而触发OnSetCursor
                    if (!this.DesignMode && this.ControlBox)
                    {
                        NonClientSizeInfo ncInfo = GetNonClientInfo(m.HWnd);
                        Point p = this.PointToScreen(new Point(0, 0));
                        Point point = new Point(MousePosition.X - p.X, MousePosition.Y - p.Y);
                        point.X += ncInfo.ClientRect.X;
                        point.Y += ncInfo.ClientRect.Y;
                        FormButton formButton = this.buttonRectangle.FirstOrDefault(x => x.Value.Contains(point)).Key;
                        if (formButton != _oldFormButton)
                        {
                            FormMouseState state = FormMouseState.None;
                            if (ncInfo.CaptionRect.Contains(point) && formButton != null)
                                state = FormMouseState.Hover;
                            DrawCaption(m.HWnd, state);
                        }
                        if (formButton != null && formButton != _oldFormButton)
                            this.toolTip1.Show(formButton.ToolTip, this, point.X + 2, ncInfo.ClientRect.Y + 2);
                        else if (formButton is null)
                            this.toolTip1.Hide(this);
                        _oldFormButton = formButton;
                    }
                    break;
                case WM_NCLBUTTONUP:// 在释放鼠标左键抬起的时候,处理一些事情
                    {
                        if (!this.DesignMode && true)
                        {
                            NonClientSizeInfo ncInfo = GetNonClientInfo(m.HWnd);
                            Point p = this.PointToScreen(new Point(0, 0));
                            Point Point = new Point(MousePosition.X - p.X, MousePosition.Y - p.Y);
                            Point.X += ncInfo.ClientRect.X;
                            Point.Y += ncInfo.ClientRect.Y;
                            foreach (FormButton Item in this.ButtonList)
                            {
                                Rectangle rect = default;
                                if (buttonRectangle.ContainsKey(Item))
                                    rect = buttonRectangle[Item];
                                if (rect != null && rect.Contains(Point))
                                {
                                    m.Msg = WM_SYSCOMMAND;
                                    if (Item == this.CloseButtonImage)
                                        m.WParam = new IntPtr(SC_CLOSE);
                                    else if (Item == this.MaximumButtonImage)
                                        m.WParam = new IntPtr(SC_MAXIMIZE);
                                    else if (Item == this.RestoreButtonImage)
                                        m.WParam = new IntPtr(SC_RESTORE);
                                    else if (Item == this.MinimumButtonImage)
                                    {
                                        if (this.MinimizeBox)
                                            m.WParam = new IntPtr(SC_MINIMIZE);
                                    }
                                    else if (Item == this.HelpButtonImage)
                                        m.WParam = new IntPtr(SC_CONTEXTHELP);
                                    else
                                        FormButtonClick?.Invoke(this, new FormButtonClickEventArgs(Item,rect));// 促发系统按钮点击事件
                                }
                            }
                        }
                        break;
                    }
                case WM_NCLBUTTONDOWN:// 在左击非客户区时响应
                    {
                        if (!this.DesignMode && this.ControlBox)
                        {
                            DrawCaption(m.HWnd, FormMouseState.Down);
                            NonClientSizeInfo ncInfo = GetNonClientInfo(m.HWnd);
                            Point p = this.PointToScreen(new Point(0, 0));
                            Point point = new Point(MousePosition.X - p.X, MousePosition.Y - p.Y);
                            point.X += ncInfo.ClientRect.X;
                            point.Y += ncInfo.ClientRect.Y;
                            foreach (FormButton Item in this.ButtonList)
                            {
                                Rectangle rect = default;
                                if (buttonRectangle.ContainsKey(Item))
                                    rect = buttonRectangle[Item];
                                if (point.Y >= ncInfo.CaptionRect.Y && point.Y <= ncInfo.ClientRect.Y)
                                    point.Y = 10;
                                else
                                    point.Y = -1;
                                if (rect != null && rect.Contains(point))
                                    return;
                            }
                        }
                        break;
                    }
                case WM_NCLBUTTONDBLCLK:  // 双击标题栏时促发
                    {
                        NonClientSizeInfo ncInfo = GetNonClientInfo(m.HWnd);
                        Point p = this.PointToScreen(new Point(0, 0));
                        Point point = new Point(MousePosition.X - p.X, MousePosition.Y - p.Y);
                        point.X += ncInfo.ClientRect.X;
                        point.Y += ncInfo.ClientRect.Y;
                        foreach (FormButton Item in this.ButtonList)
                        {
                            Rectangle rect = default;
                            if (buttonRectangle.ContainsKey(Item))
                                rect = buttonRectangle[Item];
                            if (rect != null && rect.Contains(point))
                                return;
                        }
                        break;
                    }
                case 0xAF:
                case 0xAE:
                    {
                        return;
                    }
                case 674:
                    // 未找到 674 的说明，但是需要鼠标离开时，刷新滚动条
                    DrawCaption(m.HWnd, FormMouseState.None);
                    this.toolTip1.Hide(this);
                    break;
                case WM_NCHITTEST: // 鼠标停留在非客户区执行，这里用来屏蔽系统提示
                    {
                        NonClientSizeInfo ncInfo = GetNonClientInfo(m.HWnd);
                        Point p = this.PointToScreen(new Point(0, 0));
                        Point point = new Point(MousePosition.X - p.X, MousePosition.Y - p.Y);
                        point.X += ncInfo.ClientRect.X;
                        point.Y += ncInfo.ClientRect.Y;

                        foreach (FormButton Item in this.ButtonList)
                        {
                            Rectangle rect = default;
                            if (buttonRectangle.ContainsKey(Item))
                                rect = buttonRectangle[Item];
                            if (rect != null && rect.Contains(point))
                            {
                                m.Result = new IntPtr(162);
                                return;
                            }
                        }
                        break;
                    }
                    //case 131:
                    //    if (!this.DesignMode)
                    //    {
                    //        NonClientSizeInfo info = GetNonClientInfo(m.HWnd);
                    //        RECT lpRect = (RECT)System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, typeof(RECT));
                    //        lpRect.Left -= 4;   //' 左?右?下各留 2 个像素
                    //        lpRect.Top -= 2;   //  ' 标题栏留出 31 个像素
                    //        lpRect.Right += 4;
                    //        lpRect.Bottom += 6;
                    //        System.Runtime.InteropServices.Marshal.StructureToPtr(lpRect, m.LParam, true);// 将修改完毕的区域拷贝给 lParam
                    //        m.Result = new IntPtr(0);
                    //    }
                    //    break;
            }
            base.WndProc(ref m);
        }

        private List<FormButton> ButtonList
        {
            get
            {
                List<FormButton> Items = new List<FormButton>();
                if (this.ControlBox)
                {
                    Items.Add(CloseButtonImage);
                    if (this.FormBorderStyle != System.Windows.Forms.FormBorderStyle.FixedToolWindow && this.FormBorderStyle != System.Windows.Forms.FormBorderStyle.SizableToolWindow)
                    {
                        if (this.MaximizeBox && this.WindowState != FormWindowState.Maximized)
                            Items.Add(this.MaximumButtonImage);
                        if (this.MaximizeBox && this.WindowState == FormWindowState.Maximized)
                            Items.Add(this.RestoreButtonImage);
                        if (this.MinimizeBox)
                            Items.Add(this.MinimumButtonImage);
                    }
                    if (this.HelpButton)
                        Items.Add(this.HelpButtonImage);
                    foreach (FormButton Item in this.FormButtonCollection)
                    {
                        if (Item.Visible)
                            Items.Add(Item);
                    }
                }
                return Items;
            }
        }

        private void Form_SizeChanged(object sender, EventArgs e)
        {
            if (!this.DesignMode && this.ControlBox)
                this.toolTip1.Hide(this);
        }
    }
}
